

import { Type, InjectionToken, Provider, ReflectiveInjector, Injector, Injectable } from "@angular/core";

@Injectable()
export class A<T1, T2, T3>{
    public id = +new Date();
    it: T3;

    // public static FACTORY: GenericInstanceFactory = (bt: any, ct: any) => {
    //     return new A(bt, ct);
    // };

    constructor(private bt: T1, private ct: T2) {
        console.log(`Create an instance of ${this.constructor.name} ${this.id} and generic type ${bt.constructor.name} id ${(bt as any).id}  ${ct.constructor.name} id ${(ct as any).id}`);
    }
}

@Injectable()
export class B {
    public id = +new Date();
}

@Injectable()
export class C {
    public id = +new Date();
}

export type GenericInstanceFactory = Type<any>;
export function genericInstanceTokenCreator(deps: Function[]) {
    return `GENERIC_INSTANCE_${deps.map(d => d.name).join('_')}_TOKEN`;
}

let genericInstances: Map<string, any> = new Map<string, any>();
export function genericInstanceContainerOfGet<T>(parentInjecor: Injector, factory: GenericInstanceFactory, deps: Function[]): T {
    let instanceToken = genericInstanceTokenCreator(deps);
    if (genericInstances.has(instanceToken)) {
        return genericInstances.get(instanceToken) as T;
    }

    let instance = genericInstanceCreator(parentInjecor, instanceToken, factory, deps);
    genericInstances.set(instanceToken, instanceToken);
    return instance as T;
}

function genericInstanceCreator<T>(parentInjecor: Injector, token: string, factory: GenericInstanceFactory, deps: any[]): T {
    let provider: Provider = {
        provide: token,
        useFactory: (...args: any[]) => new factory(...args),
        deps: deps
    };

    let childInjector = ReflectiveInjector.resolveAndCreate([provider], parentInjecor);
    let genericInstance = childInjector.get(token) as T;

    return genericInstance;
}